// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Collections;
using System.Globalization;
using System.Text;
using System.Diagnostics;
using Microsoft.TeamFoundation.Migration.VersionControl;
using Microsoft.TeamFoundation.Migration.Toolkit;
using Microsoft.TeamFoundation.Migration.VersionControl.Wss;
using Microsoft.TeamFoundation.Migration.Toolkit.VC;
using Microsoft.TeamFoundation.Migration.Toolkit.Wit;
using System.IO;
using System.Threading;
using System.Net;
using System.Data.SqlClient;

namespace MigrationConsole
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1053:StaticHolderTypesShouldNotHaveConstructors")]
    public class Program
    {
        static int Main(string[] args)
        {
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
            AppDomain.CurrentDomain.ProcessExit += new EventHandler(CurrentDomain_ProcessExit);
            Console.CancelKeyPress += new ConsoleCancelEventHandler(CtrlCHandler);

            try
            {
                return MainImpl(args);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                Trace.WriteLine(e.ToString());

                // we're already aborting...
                TraceManager.AbortOnError = false;

#if DEBUG
                if(Debugger.IsAttached)
                {
                    throw;
                }
#endif

                return 100;
            }
        }

        private static void ProcessCommandLine(string[] args)
        {
            CommandLine cl = new CommandLine();
            cl.ReceiveNameValuePair += RegisterNameValuePair;
            cl.UnknownArgument += UnnamedArgument;
            cl.ProcessArgs(args);
            if (string.IsNullOrEmpty(s_configFile))
            {
                Console.WriteLine("Invalid arguments - configuration file is not provided.");
                CommandLine.Usage();
                Environment.Exit(1);
            }
        }

        public static int MainImpl(string[] args)
        {
            using (TraceManager.StartLogicalOperation("Main"))
            {
                TraceManager.EnterMethod();

                ProcessCommandLine(args);
                Initialize();

                string runningMode = m_config.GetValue<string>("Mode", "Full");
                int waitTime = 60;
                try
                {
                    waitTime = int.Parse(m_config.GetValue<string>("Wait", "60"));
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                    CommandLine.Usage();
                    return -1;
                }
                
                List<IMigrationSession> sessionList = new List<IMigrationSession>();

                bool allSessionCompleted = false;


                if (Config.VC != null)
                {
                    // VC session
                    foreach (VersionControlSession session in Config.VC.Sessions.Values)
                    {
                        registerVcEvents(session);
                        sessionList.Add(session);
                    }
                }

                // WIT session
                if (Config.Wit != null)
                {
                    foreach (WorkItemTrackingSession session in Config.Wit.Sessions.Values)
                    {
                        registerWitEvents(session);
                        sessionList.Add(session);
                    }
                }

                foreach( IMigrationSession session in sessionList)
                {
                    if (string.Compare(runningMode, "Full", StringComparison.InvariantCultureIgnoreCase) == 0)
                    {
                        session.SynchronizeFull();
                    }
                    else if (string.Compare(runningMode, "SourceToTfs", StringComparison.InvariantCultureIgnoreCase) == 0)
                    {
                        session.Synchronize(SystemType.Other);
                    }
                    else if (string.Compare(runningMode, "TfsToSource", StringComparison.InvariantCultureIgnoreCase) == 0)
                    {
                        session.Synchronize(SystemType.Tfs);
                    }
                    else if (string.Compare(runningMode, "Service", StringComparison.InvariantCultureIgnoreCase) == 0)
                    {
                        Console.WriteLine(MigrationConsoleResources.ServiceModeSelected);
                        session.Start();
                    }
                    else
                    {
                        Console.WriteLine(MigrationConsoleResources.UnknownRunningMode);
                        CommandLine.Usage();
                        Environment.Exit(1);
                    }
                }

                while (!allSessionCompleted)
                {
                    stopifRequested(sessionList);
                    allSessionCompleted = true;
                    foreach (IMigrationSession session in sessionList)
                    {
                        if (session.IsRunning)
                        {
                            allSessionCompleted = false;
                            break;
                        }
                    }
                    if (!allSessionCompleted)
                    {
                        Thread.Sleep(waitTime * 1000);
                    }
                }
                TraceManager.ExitMethod(0);
            }

            return 0;
        }
        private static void CtrlCHandler(object sender, ConsoleCancelEventArgs args)
        {
            if (args.SpecialKey == ConsoleSpecialKey.ControlC)
            {
                stopRequested = true;
                args.Cancel = true;
                Console.WriteLine(MigrationConsoleResources.StopAllSessions);
                Trace.WriteLine("Stopping all sessions");
            }
        }

        static private void stopifRequested(List<IMigrationSession> sessionList)
        {
            // Stop all sessions if the input from standard input is 'carriage-return' (13)
            if (stopRequested)
            {
                foreach (IMigrationSession session in sessionList)
                {
                    session.Stop();
                }
            }

        }

        private static void registerVcEvents(VersionControlSession vcSession )
        {
            vcSession.AnalyzingChangeComplete += new EventHandler<VersionControlEventArgs>(s_AnalyzingChangeComplete);
            vcSession.AnalyzingChangeStarting += new EventHandler<VersionControlEventArgs>(s_AnalyzingChangeStarting);
            vcSession.MigratingChangeComplete += new EventHandler<VersionControlEventArgs>(s_MigratingChangeComplete);
            vcSession.MigratingChangeStarting += new EventHandler<VersionControlEventArgs>(s_MigratingChangeStarting);
            vcSession.SessionAborted += new EventHandler<MigrationSessionEventArgs>(SessionEvent);
            vcSession.SessionComplete += new EventHandler<MigrationSessionEventArgs>(SessionEvent);
            vcSession.SessionError += new EventHandler<MigrationSessionEventArgs>(SessionEvent);
            vcSession.SessionStart += new EventHandler<MigrationSessionEventArgs>(SessionEvent);
        }

        private static void registerWitEvents(WorkItemTrackingSession witSession)
        {
            witSession.WorkItemMigrationStart += new EventHandler<WorkItemMigrationEventArgs>(SessionEvent);
            witSession.WorkItemMigrationComplete += new EventHandler<MigrationCompleteEventArgs>(SessionEvent);
            witSession.WorkItemAnalysisComplete += new EventHandler<AnalysisCompleteEventArgs>(SessionEvent);
            witSession.WorkItemMigrationFailed += new EventHandler<WorkItemMigrationEventArgs>(SessionEvent);
            witSession.WorkItemFieldConflict += new EventHandler<FieldConflictEventArgs>(SessionEvent);
        }

        static void SessionEvent(object sender, MigrationEventArgs e)
        {
            string msg = string.Format(CultureInfo.InvariantCulture, 
                "[{0} {1}] {2}",            
                e.Time.ToUniversalTime(),
                sender.ToString(),
                e.Description);

            if (e.Exception != null)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                msg = string.Format(CultureInfo.InvariantCulture,
                    "{0} Exception: {1}",
                    msg,
                    e.Exception.ToString());
                Console.WriteLine(msg);
                Console.ResetColor();
            }
            else
            {
                Console.WriteLine(msg);
            }
        }

        static void s_AnalyzingChangeStarting(object sender, MigrationEventArgs e)
        {
            Console.WriteLine("[{0}] - Starting Analysis of {1}", ((IMigrationSession)sender).Id, e.Description);
        }

        static void s_AnalyzingChangeComplete(object sender, MigrationEventArgs e)
        {
            Console.WriteLine("[{0}] - Completed Analysis of {1}", ((IMigrationSession)sender).Id, e.Description);
        }

        static void s_MigratingChangeComplete(object sender, MigrationEventArgs e)
        {
            Console.WriteLine("[{0}] - Completed Migration of {1}", ((IMigrationSession)sender).Id, e.Description);
        }

        static void s_MigratingChangeStarting(object sender, MigrationEventArgs e)
        {
            Console.WriteLine("[{0}] - Starting Migration of {1}", ((IMigrationSession)sender).Id, e.Description);
        }

        private static void CreateSchema()
        {
            DataAccessManager.Current.CreateSchema(false);
        }

        public static void Abort()
        {
            m_canceled = true;
        }

        public static void ThrowIfCanceled()
        {
            if (m_canceled)
            {
                throw new MigrationException();
            }
        }

        public static void Initialize()
        {
            try
            {
                using (FileStream fs = File.Open(s_configFile, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    m_config = MigrationConfiguration.Load(fs);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(string.Format(MigrationConsoleResources.Culture, MigrationConsoleResources.ConfigFileError, s_configFile));
                Console.WriteLine(e.Message);
                Trace.TraceError(e.Message);
                CommandLine.Usage();
                Environment.Exit(1);
                throw;
            }

            foreach (DictionaryEntry configEntry in configTable)
            {
                Config.AddSetting((string)configEntry.Key, (string)configEntry.Value, true);
            }


            TraceManager.ShowMethodDetails = m_config.GetValue<bool>("ShowMethodDetails", false);

            try
            {
                CreateSchema();
            }
            catch (SqlException se)
            {
                TraceManager.TraceException(se);

                throw new MigrationException(
                    string.Format(CultureInfo.InstalledUICulture, "Unable to create the SQL schema due to the following failure: {0}", se.Message),
                    se);
            }
        }

        static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            if (e.ExceptionObject != null)
            {
                TraceManager.TraceError(e.ExceptionObject.ToString());
                TraceManager.TraceException((Exception)e.ExceptionObject);
            }
            else
            {
                TraceManager.TraceError("Unhandled exception was null");
            }

            Environment.Exit(100);
        }

        static void CurrentDomain_ProcessExit(object sender, EventArgs e)
        {
            TraceManager.TraceInformation("The program is shutting down!");
        }

        public static MigrationConfiguration Config
        {
            get
            {
                return m_config;
            }
        }

        static void UnnamedArgument(string argument)
        {
            if (s_configFile == null && !string.IsNullOrEmpty(argument))
            {
                s_configFile = argument;
            }
            else
            {
                Console.WriteLine("Invalid free argument : {0}.", argument);
                CommandLine.Usage();
                Environment.Exit(1);
            }
        }

        static void RegisterNameValuePair(string key, string value)
        {
            TraceManager.TraceInformation("Adding command line configuration: {0} = {1}", key, value);
            configTable.Add(key, value);
        }

        static MigrationConfiguration m_config;
        static bool m_canceled;
        static string s_configFile;
        static Hashtable configTable = new Hashtable();
        static bool stopRequested = false;
    }
}
